package org.mspring.mlog.search.engine;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.MultiReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.mspring.mlog.entity.Post;
import org.mspring.mlog.search.SearchHelper;
import org.mspring.mlog.search.Searchable;
import org.mspring.platform.dao.support.Page;
import org.mspring.platform.utils.StringUtils;
import org.wltea.analyzer.lucene.IKAnalyzer;

@SuppressWarnings({ "unchecked", "rawtypes" })
public class LuceneSearchEngine extends AbstractSearchEngine {

    private static final Logger log = Logger.getLogger(LuceneSearchEngine.class);
    /**
     * 索引存放路径
     */
    private String indexPath;
    /**
     * 分词器
     */
    private Analyzer analyzer = new IKAnalyzer();

    public String getIndexPath() {
        return indexPath;
    }

    public void setIndexPath(String indexPath) {
        this.indexPath = indexPath;
    }

    public Analyzer getAnalyzer() {
        return analyzer;
    }

    public void setAnalyzer(Analyzer analyzer) {
        this.analyzer = analyzer;
    }

    @Override
    public synchronized void doIndex(Searchable... searchables) throws Exception {
        // TODO Auto-generated method stuba
        if (searchables == null || searchables.length == 0) {
            return;
        }
        doIndex(Arrays.asList(searchables));
    }

    @Override
    public synchronized void doIndex(List<? extends Searchable> searchables) throws Exception {
        // TODO Auto-generated method stub
        if (searchables == null || searchables.size() == 0) {
            return;
        }
        IndexWriter writer = getWriter(searchables.get(0).getClass());
        try {
            for (Searchable searchable : searchables) {
                Document doc = SearchHelper.obj2doc(searchable);
                writer.addDocument(doc);
            }
            writer.commit();
        } catch (Exception e) {
            // TODO: handle exception
            log.error("索引失败", e);
        } finally {
            writer.close();
        }
    }

    @Override
    public synchronized void deleteIndex(Searchable... beans) throws Exception {
        // TODO Auto-generated method stub
        if (beans == null || beans.length == 0) {
            return;
        }
        deleteIndexs(Arrays.asList(beans));
    }

    @Override
    public synchronized void deleteIndexs(List<? extends Searchable> beans) throws Exception {
        // TODO Auto-generated method stub
        if (beans == null || beans.size() == 0) {
            return;
        }
        IndexWriter writer = getWriter(beans.get(0).getClass());
        try {
            for (Searchable bean : beans) {
                writer.deleteDocuments(new Term("id", String.valueOf(bean.documentId())));
            }
            writer.commit();
        } catch (Exception e) {
            log.error("删除索引失败", e);
        } finally {
            writer.close();
        }
    }

    @Override
    public synchronized void updateIndex(Searchable... searchables) throws Exception {
        // TODO Auto-generated method stub
        if (searchables == null || searchables.length == 0) {
            return;
        }
        updateIndexs(Arrays.asList(searchables));
    }

    @Override
    public synchronized void optimize(Class<? extends Searchable> clazz) throws Exception {
        // TODO Auto-generated method stub
        IndexWriter writer = getWriter(clazz);
        try {
            writer.forceMerge(1);
            writer.commit();
        } catch (Exception e) {
            log.error("优化索引失败", e);
        } finally {
            writer.close();
        }
    }

    @Override
    public synchronized void updateIndexs(List<? extends Searchable> searchables) throws Exception {
        // TODO Auto-generated method stub
        if (searchables == null || searchables.size() == 0) {
            return;
        }
        IndexWriter writer = getWriter(searchables.get(0).getClass());
        try {
            for (Searchable searchable : searchables) {
                Document doc = SearchHelper.obj2doc(searchable);
                writer.updateDocument(new Term("id", String.valueOf(searchable.documentId())), doc);
            }
            writer.commit();
        } catch (Exception e) {
            log.error("更新索引失败", e);
        } finally {
            writer.close();
            writer = null;
        }
    }

    public static void main(String[] args) throws Exception {
        LuceneSearchEngine searchEngine = new LuceneSearchEngine();
        searchEngine.setIndexPath("D:/index");
        Page page = new Page();
        // searchEngine.doSearch(Post.class, "java", new String[] { "title"/*,
        // "summary", "content"*/ }, false, page);
        searchEngine.doSearch(Post.class, "java", new String[] { "title", "summary", "content" }, false, page);
        List list = page.getResult();
        System.out.println(list.size());
        for (Object object : list) {
            Post post = (Post) object;
            System.out.println(post.getTitle());
            // if (post.getTitle().equals("Java")) {
            // System.out.println("$$$$$$$$$$$$$$$$$$$$$$");
            // }
        }
    }

    @Override
    public Page doSearch(Class<? extends Searchable> clazz, String keyword, String[] fields, boolean isHighlighter, Page page) throws Exception {
        // TODO Auto-generated method stub
        if (StringUtils.isBlank(keyword) || fields == null || fields.length == 0) {
            return null;
        }

        QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_44, fields, analyzer);
        return doSearch(clazz, queryParser.parse(keyword), isHighlighter, page);
        // Filter filter = new QueryWrapperFilter(new WildcardQuery(new
        // Term("status", Post.Status.PUBLISH)));
        // searcher.search(queryParser.parse(keyword), filter,
        // page.getPageSize() * 10);
    }

    @Override
    public Page doSearch(Class<? extends Searchable> clazz, Query query, boolean isHighlighter, Page page) throws Exception {
        // TODO Auto-generated method stub
        IndexSearcher searcher = getSearcher(clazz);
        try {
            TopDocs hits = searcher.search(query, page.getPageSize() * 10);

            int nBegin = page.getFirst();
            int nEnd = Math.min(nBegin + page.getPageSize(), hits.scoreDocs.length);

            List<Searchable> results = new ArrayList<Searchable>();
            for (int i = nBegin; i < nEnd; i++) {
                ScoreDoc s_doc = (ScoreDoc) hits.scoreDocs[i];
                Document doc = searcher.doc(s_doc.doc);
                Searchable searchable = SearchHelper.getSearchableInstance(doc);
                if (searchable != null && !results.contains(searchable)) {
                    searchable = searchable.document2Object(doc);
                    results.add(searchable);
                }
            }
            page.setResult(results);
            if (page.isAutoCount()) {
                Long totalCount = count(clazz, query);
                page.setTotalCount(totalCount);
            }
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
            page.setResult(null);
        }
        return page;
    }

    @Override
    public Page doSearch(List<Class<? extends Searchable>> classes, String keyword, String[] fields, boolean isHighlighter, Page page) throws Exception {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Long count(Class<? extends Searchable> clazz, Query query) throws Exception {
        // TODO Auto-generated method stub
        if (clazz == null || query == null) {
            return new Long(-1);
        }
        IndexSearcher searcher = getSearcher(clazz);
        try {
            TotalHitCountCollector thcc = new TotalHitCountCollector();
            searcher.search(query, thcc);
            return Long.valueOf(thcc.getTotalHits());
        } catch (IOException e) {
            return new Long(-1);
        }
    }

    @Override
    public Long count(List<Class<? extends Searchable>> classes, String keyword, String[] fields) throws Exception {
        // TODO Auto-generated method stub
        return null;
    }

    /***************************** 受保护的方法 ******************************/
    // protected final static int MAX_COUNT = 1000;

    /**
     * 获取索引写入器
     * 
     * @param clazz
     * @return
     * @throws IOException
     */
    protected IndexWriter getWriter(Class<? extends Searchable> clazz) throws IOException {
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_44, analyzer);
        config.setOpenMode(OpenMode.CREATE_OR_APPEND);
        return new IndexWriter(getIndexDirectory(clazz), config);
    }

    /**
     * 单个资料库的搜索
     * 
     * @param objClass
     * @return
     * @throws IOException
     */
    protected IndexSearcher getSearcher(Class<? extends Searchable> clazz) throws IOException {
        return new IndexSearcher(DirectoryReader.open(getIndexDirectory(clazz)));
    }

    /**
     * 多个资料库的搜索
     * 
     * @param objClasses
     * @return
     * @throws IOException
     */
    protected IndexSearcher getSearchers(List<Class<? extends Searchable>> classes) throws IOException {
        IndexReader[] readers = new IndexReader[classes.size()];
        int idx = 0;
        for (Class<? extends Searchable> objClass : classes) {
            readers[idx++] = DirectoryReader.open(getIndexDirectory(objClass));
        }
        return new IndexSearcher(new MultiReader(readers, true));
    }

    /**
     * 获取索引文件夹
     * 
     * @param clazz
     * @return
     * @throws IOException
     */
    protected Directory getIndexDirectory(Class<? extends Searchable> clazz) throws IOException {
        String dirPath = indexPath + File.separator + clazz.getSimpleName();
        File dirFile = new File(dirPath);
        return FSDirectory.open(dirFile);
    }

}
